<!DOCTYPE html>
<meta charset="utf-8">
<meta property="og:image" itemprop="image primaryImageOfPage" content="https://hajimehoshi.github.io/ebiten/images/examples/piano.png">
<meta name="description" content="Ebiten example - piano">
<link rel="shortcut icon" href="../favicon.png" type="image/png" >
<link rel="icon" href="../favicon.png" type="image/png" >
<title>Ebiten example - piano</title>

<link rel="stylesheet" href="../stylesheets/bootstrap.min.css">
<link rel="stylesheet" href="../stylesheets/highlight-github.css">
<link rel="stylesheet" href="../stylesheets/ebiten.css">
<script src="../scripts/googleanalytics.js"></script>

<nav class="navbar"><div class="container">
  <nav class="d-flex flex-row" style="width: 100%;">
    <div class="nav mr-auto"><a class="navbar-brand" href="../"><img src="../images/logo_white.svg" alt="EBITEN"></a></div>
    <ul class="nav">
      <li class="nav-item"><a class="nav-link" href="https://github.com/hajimehoshi/ebiten">GitHub</a></li>
      <li class="nav-item"><a class="nav-link" href="https://godoc.org/github.com/hajimehoshi/ebiten">GoDoc</a></li>
      <li class="nav-item"><a class="nav-link" href="https://github.com/hajimehoshi/ebiten/wiki">Wiki</a>
      <li class="nav-item"><a class="nav-link" href="https://ebiten-playground.github.io/">Playground</a>
    </ul>
  </nav>
</div></nav>

<main><div class="container">

  <h2>Ebiten example - piano</h2>
  <iframe src="piano.content.html" width="640" height="480"></iframe>
  <div class="card"><pre class="card-body"><code class="language-go">// &#43;build example

package main

import (
        &#34;fmt&#34;
        &#34;image/color&#34;
        &#34;io/ioutil&#34;
        &#34;log&#34;
        &#34;math&#34;

        &#34;github.com/golang/freetype/truetype&#34;
        &#34;golang.org/x/image/font&#34;

        &#34;github.com/hajimehoshi/ebiten&#34;
        &#34;github.com/hajimehoshi/ebiten/audio&#34;
        &#34;github.com/hajimehoshi/ebiten/ebitenutil&#34;
        &#34;github.com/hajimehoshi/ebiten/inpututil&#34;
        &#34;github.com/hajimehoshi/ebiten/text&#34;
)

var (
        arcadeFont font.Face
)

func init() {
        f, err := ebitenutil.OpenFile(&#34;_resources/fonts/arcade_n.ttf&#34;)
        if err != nil {
                log.Fatal(err)
        }
        defer f.Close()

        b, err := ioutil.ReadAll(f)
        if err != nil {
                log.Fatal(err)
        }

        tt, err := truetype.Parse(b)
        if err != nil {
                log.Fatal(err)
        }

        const (
                arcadeFontSize = 8
                dpi            = 72
        )
        arcadeFont = truetype.NewFace(tt, &amp;truetype.Options{
                Size:    arcadeFontSize,
                DPI:     dpi,
                Hinting: font.HintingFull,
        })
}

const (
        screenWidth  = 320
        screenHeight = 240
        sampleRate   = 44100
        baseFreq     = 220
)

var audioContext *audio.Context

func init() {
        var err error
        audioContext, err = audio.NewContext(sampleRate)
        if err != nil {
                log.Fatal(err)
        }
}

// pianoAt returns an i-th sample of piano with the given frequency.
func pianoAt(i int, freq float64) float64 {
        // Create piano-like waves with multiple sin waves.
        amp := []float64{1.0, 0.8, 0.6, 0.4, 0.2}
        x := []float64{4.0, 2.0, 1.0, 0.5, 0.25}
        v := 0.0
        for j := 0; j &lt; len(amp); j&#43;&#43; {
                // Decay
                a := amp[j] * math.Exp(-5*float64(i)*freq/baseFreq/(x[j]*sampleRate))
                v &#43;= a * math.Sin(2.0*math.Pi*float64(i)*freq*float64(j&#43;1)/sampleRate)
        }
        return v / 5.0
}

// toBytes returns the 2ch little endian 16bit byte sequence with the given left/right sequence.
func toBytes(l, r []int16) []byte {
        if len(l) != len(r) {
                panic(&#34;len(l) must equal to len(r)&#34;)
        }
        b := make([]byte, len(l)*4)
        for i := range l {
                b[4*i] = byte(l[i])
                b[4*i&#43;1] = byte(l[i] &gt;&gt; 8)
                b[4*i&#43;2] = byte(r[i])
                b[4*i&#43;3] = byte(r[i] &gt;&gt; 8)
        }
        return b
}

var (
        pianoNoteSamples       = map[int][]byte{}
        pianoNoteSamplesInited = false
        pianoNoteSamplesInitCh = make(chan struct{})
)

func init() {
        // Initialize piano data.
        // This takes a little long time (especially on browsers),
        // so run this asynchronously and notice the progress.
        go func() {
                // Create a reference data and use this for other frequency.
                const refFreq = 110
                length := 4 * sampleRate * baseFreq / refFreq
                refData := make([]int16, length)
                for i := 0; i &lt; length; i&#43;&#43; {
                        refData[i] = int16(pianoAt(i, refFreq) * math.MaxInt16)
                }

                for i := range keys {
                        freq := baseFreq * math.Exp2(float64(i-1)/12.0)

                        // Clculate the wave data for the freq.
                        length := 4 * sampleRate * baseFreq / int(freq)
                        l := make([]int16, length)
                        r := make([]int16, length)
                        for i := 0; i &lt; length; i&#43;&#43; {
                                idx := int(float64(i) * freq / refFreq)
                                if len(refData) &lt;= idx {
                                        break
                                }
                                l[i] = refData[idx]
                        }
                        copy(r, l)
                        n := toBytes(l, r)
                        pianoNoteSamples[int(freq)] = n
                }
                close(pianoNoteSamplesInitCh)
        }()
}

// playNote plays piano sound with the given frequency.
func playNote(freq float64) {
        f := int(freq)
        p, _ := audio.NewPlayerFromBytes(audioContext, pianoNoteSamples[f])
        p.Play()
}

var (
        pianoImage *ebiten.Image
)

func init() {
        pianoImage, _ = ebiten.NewImage(screenWidth, screenHeight, ebiten.FilterDefault)

        const (
                keyWidth = 24
                y        = 48
        )

        whiteKeys := []string{&#34;A&#34;, &#34;S&#34;, &#34;D&#34;, &#34;F&#34;, &#34;G&#34;, &#34;H&#34;, &#34;J&#34;, &#34;K&#34;, &#34;L&#34;}
        for i, k := range whiteKeys {
                x := i*keyWidth &#43; 36
                height := 112
                ebitenutil.DrawRect(pianoImage, float64(x), float64(y), float64(keyWidth-1), float64(height), color.White)
                text.Draw(pianoImage, k, arcadeFont, x&#43;8, y&#43;height-8, color.Black)
        }

        blackKeys := []string{&#34;Q&#34;, &#34;W&#34;, &#34;&#34;, &#34;R&#34;, &#34;T&#34;, &#34;&#34;, &#34;U&#34;, &#34;I&#34;, &#34;O&#34;}
        for i, k := range blackKeys {
                if k == &#34;&#34; {
                        continue
                }
                x := i*keyWidth &#43; 24
                height := 64
                ebitenutil.DrawRect(pianoImage, float64(x), float64(y), float64(keyWidth-1), float64(height), color.Black)
                text.Draw(pianoImage, k, arcadeFont, x&#43;8, y&#43;height-8, color.White)
        }
}

var (
        keys = []ebiten.Key{
                ebiten.KeyQ,
                ebiten.KeyA,
                ebiten.KeyW,
                ebiten.KeyS,
                ebiten.KeyD,
                ebiten.KeyR,
                ebiten.KeyF,
                ebiten.KeyT,
                ebiten.KeyG,
                ebiten.KeyH,
                ebiten.KeyU,
                ebiten.KeyJ,
                ebiten.KeyI,
                ebiten.KeyK,
                ebiten.KeyO,
                ebiten.KeyL,
        }
)

func update(screen *ebiten.Image) error {
        // The piano data is still being initialized.
        // Get the progress if available.
        if !pianoNoteSamplesInited {
                select {
                case &lt;-pianoNoteSamplesInitCh:
                        pianoNoteSamplesInited = true
                default:
                }
        }

        if pianoNoteSamplesInited {
                for i, key := range keys {
                        if !inpututil.IsKeyJustPressed(key) {
                                continue
                        }
                        playNote(baseFreq * math.Exp2(float64(i-1)/12.0))
                }
        }

        if ebiten.IsRunningSlowly() {
                return nil
        }

        screen.Fill(color.RGBA{0x80, 0x80, 0xc0, 0xff})
        screen.DrawImage(pianoImage, nil)

        ebitenutil.DebugPrint(screen, fmt.Sprintf(&#34;FPS: %0.2f&#34;, ebiten.CurrentFPS()))
        return nil
}

func main() {
        if err := ebiten.Run(update, screenWidth, screenHeight, 2, &#34;Piano (Ebiten Demo)&#34;); err != nil {
                log.Fatal(err)
        }
}
</code></pre></div>

</div></main>

<footer><div class="container">
  <p>© 2013 Hajime Hoshi</p>
  <p>Code is licensed under <a href="https://github.com/hajimehoshi/ebiten/blob/master/LICENSE">the Apache License 2.0</a>.</p>
  <p>The content of this page is licensed under <a href="https://creativecommons.org/licenses/by/4.0/">the Creative Commons Attribution 4.0 License</a>.</p>
</div></footer>

<script src="../scripts/highlight.pack.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
